﻿using PuSuoPai;
using ServiceStack.Redis.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TeZhanDui
{
    public enum GAME_STATE
    {
        DISTRIBUTE,
        OFFEND,
        DEFEND,
        COMPARE,
        END,
        CLOSE,
        
        MAX
    }

    public class Game
    {
        public Room room = null;
        public List<GamePlayer> players { get; set; }
        CardPository cardPository = null;
        public GAME_STATE state { get; set; }
        GameTimer timer = null;
        public Playing offender { get; set; }
        public List<Playing> defenders { get; set; }
        DateTime startTime = DateTime.Now;
        List<PlayerTezhandui> playersTezhandui = null;
        List<int> playersPoints = null;
        List<string> team1 = null;
        List<string> team2 = null;
        TeamTezhandui teamTezhandui1 = null;
        TeamTezhandui teamTezhandui2 = null;

        public Game(Room room)
        {
            this.room = room;
            players = new List<GamePlayer>();
            DeterminePlayers();
            cardPository = new CardPository();
            state = GAME_STATE.DISTRIBUTE;
            timer = new GameTimer(this);
            offender = new Playing();
            defenders = new List<Playing>();
            for (int i = 0; i < (players.Count / 2); i++)
            {
                Playing defender = new Playing();
                defenders.Add(defender);
            }
            DistributeCards();

            PreparePlayerTezhanduiInfo();

            if (this.room.type == ROOM_TYPE.ROOM_MATCH)
            {
                PrepareTeamTezhanduiInfo();
            }
        }

        void PrepareTeamTezhanduiInfo()
        {
            team1 = new List<string>();
            team2 = new List<string>();
            for (int i = 0; i < (players.Count / 2); i++)
            {
                team1.Add(players[i].player.Name);
                team2.Add(players[i + (players.Count / 2)].player.Name);
            }
            team1.Sort();
            team2.Sort();
            using (var redis = RedisMgr.Instance.GetClient())
            {
                IRedisTypedClient<TeamTezhandui> redisT = redis.As<TeamTezhandui>();
                teamTezhandui1 = redisT.GetFromHash(team1[0] + "_" + team1[1] + "_" + team1[2]);
                if (teamTezhandui1.Id == null)
                {
                    teamTezhandui1 = new TeamTezhandui();
                    teamTezhandui1.Id = team1[0] + "_" + team1[1] + "_" + team1[2];
                    redisT.StoreAsHash(teamTezhandui1);
                    Console.WriteLine("teamTezhandui1 " + teamTezhandui1.Id + "added to DB");
                }
                teamTezhandui2 = redisT.GetFromHash(team2[0] + "_" + team2[1] + "_" + team2[2]);
                if (teamTezhandui2.Id == null)
                {
                    teamTezhandui2 = new TeamTezhandui();
                    teamTezhandui2.Id = team2[0] + "_" + team2[1] + "_" + team2[2];
                    redisT.StoreAsHash(teamTezhandui2);
                    Console.WriteLine("teamTezhandui2 " + teamTezhandui2.Id + "added to DB");
                }
            }
        }

        void PreparePlayerTezhanduiInfo()
        {
            playersTezhandui = new List<PlayerTezhandui>();
            playersPoints = new List<int>();
            for (int i = 0; i < players.Count; i++)
            {
                playersPoints.Add(players[i].player.Points);
                using (var redis = RedisMgr.Instance.GetClient())
                {
                    IRedisTypedClient<PlayerTezhandui> redisT = redis.As<PlayerTezhandui>();
                    PlayerTezhandui playerTezhandui = redisT.GetFromHash(players[i].player.Name);
                    if (playerTezhandui.Id == null)
                    {
                        playerTezhandui = new PlayerTezhandui();
                        playerTezhandui.Id = players[i].player.Name;
                        redisT.StoreAsHash(playerTezhandui);
                        Console.WriteLine("playerTezhandui " + players[i].player.Name + "added to DB");
                    }
                    playersTezhandui.Add(playerTezhandui);
                    //Console.WriteLine("playerTezhandui " + players[i].player.Name + "prepared");
                }
            }
        }

        void DeterminePlayers()
        {
            int playersCount = 0;
            for (int i = 0; i < 6; i++)
            {
                if (room.positions[i].player != null)
                {
                    GamePlayer gamePlayer = new GamePlayer();
                    gamePlayer.playerIdx = playersCount;
                    gamePlayer.player = room.positions[i].player;
                    players.Add(gamePlayer);
                    playersCount++;
                }
            }
        }

        void DistributeCards()
        {
            for (int i = 0; i < players.Count; i++)
            {
                for (int j = 0; j < CardParameters.PER_PLAYER_CARD_COUNT; j++)
                {
                    players[i].rest.Add(cardPository.cards[cardPository.curCard]);
                    cardPository.curCard++;
                }
            }
        }

        void DetermineOffenderDefenders()
        {
            if (offender.playerIdx == -1)
            {
                offender.playerIdx = 0;
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    defenders[i].playerIdx = (players.Count / 2) + i;
                }
            }
            else
            {
                offender.playerIdx++;
                if (offender.playerIdx >= players.Count)
                {
                    offender.playerIdx = 0;
                }
                offender.output = -1;
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    if (offender.playerIdx < (players.Count / 2))
                    {
                        defenders[i].playerIdx = (players.Count / 2) + i;
                    }
                    else
                    {
                        defenders[i].playerIdx = i;
                    }
                    defenders[i].output = -1;
                }
            }
        }

        Boolean CheckEnd()
        {
            if (players[offender.playerIdx].rest.Count == 0)
            {
                return true;
            }
            foreach (Playing defender in defenders)
            {
                if (players[defender.playerIdx].rest.Count == 0)
                {
                    return true;
                }
            }
            return false;
        }

        void OutputCards()
        {
            if (state == GAME_STATE.OFFEND)
            {
                if (offender.output == -1)
                {
                    //output a random card
                    offender.output = players[offender.playerIdx].rest[0];
                    players[offender.playerIdx].rest.RemoveAt(0);
                }
                else
                {
                    players[offender.playerIdx].rest.Remove(offender.output);
                }
            }
            else
            {
                foreach (Playing defender in defenders)
                {
                    if (defender.output == -1)
                    {
                        //output a random card
                        defender.output = players[defender.playerIdx].rest[0];
                        players[defender.playerIdx].rest.RemoveAt(0);
                    }
                    else
                    {
                        players[defender.playerIdx].rest.Remove(defender.output);
                    }
                }
            }
        }

        void CompareCards()
        {
            int defendersSum = 0;
            for (int i = 0; i < defenders.Count; i++)
            {
                defendersSum += defenders[i].output;
            }
            //move offender's card to his win
            players[offender.playerIdx].win.Add(offender.output);
            players[offender.playerIdx].rest.Remove(offender.output);

            if (offender.output * defenders.Count > defendersSum)
            {
                foreach (Playing defender in defenders)
                {
                    if (offender.output > defender.output)
                    {
                        //offender take defender's card
                        players[offender.playerIdx].win.Add(defender.output);
                        players[defender.playerIdx].rest.Remove(defender.output);
                    }
                    else
                    {
                        //move defender's card to his win
                        players[defender.playerIdx].win.Add(defender.output);
                        players[defender.playerIdx].rest.Remove(defender.output);
                    }
                }
            }
            else
            {
                foreach (Playing defender in defenders)
                {
                    //move defender's card to his win
                    players[defender.playerIdx].win.Add(defender.output);
                    players[defender.playerIdx].rest.Remove(defender.output);
                }
            }

            foreach (Playing defender in defenders)
            {
                if (defender.output > offender.output)
                {
                    //get a new card from pository
                    players[defender.playerIdx].rest.Add(cardPository.cards[cardPository.curCard]);
                    cardPository.curCard++;
                }
            }
        }

        void CaculateResult()
        {
            //first half win or second half win
            int half1 = 0;
            int half2 = 0;
            int half1Total = 0;
            int half2Total = 0;
            for (int i = 0; i < (players.Count / 2); i++)
            {
                half1 += players[i].win.Count;
                half2 += players[i + (players.Count / 2)].win.Count;
            }

            if (half1 == half2)
            {
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    for (int j = 0; j < (players[i].win.Count); j++)
                    {
                        half1Total += players[i].win[j];
                    }
                }
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    for (int j = 0; j < (players[i + (players.Count / 2)].win.Count); j++)
                    {
                        half2Total += players[i + (players.Count / 2)].win[j];
                    }
                }
            }

            if (half1 > half2 || half1Total > half2Total)
            {
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    players[i].player.Points += players.Count * 100;
                    players[i + (players.Count / 2)].player.Points -= players.Count * 100;
                }
                //best fighter
                int bestIdx = 0;
                int bestWin = players[0].win.Count;
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    if (players[i].win.Count > bestWin)
                    {
                        bestIdx = i;
                        bestWin = players[i].win.Count;
                    }
                }
                int bestWinCount = 0;
                for (int i = 0; i < (players.Count / 2); i++)
                {
                    if (players[i].win.Count == bestWin)
                    {
                        bestWinCount++;
                    }
                }
                if (bestWinCount == 1)
                {
                    players[bestIdx].player.Points += players.Count * 100 / 2;
                    for (int i = 0; i < (players.Count / 2); i++)
                    {
                        players[i + (players.Count / 2)].player.Points -= 100;
                    }
                }
            }
            else if (half1 < half2 || half1Total < half2Total)
            {
                for (int i = players.Count / 2; i < players.Count; i++)
                {
                    players[i].player.Points += players.Count * 100;
                    players[i - (players.Count / 2)].player.Points -= players.Count * 100;
                }
                //best fighter
                int bestIdx = players.Count / 2;
                int bestWin = players[bestIdx].win.Count;
                for (int i = players.Count / 2; i < players.Count; i++)
                {
                    if (players[i].win.Count > bestWin)
                    {
                        bestIdx = i;
                        bestWin = players[i].win.Count;
                    }
                }
                int bestWinCount = 0;
                for (int i = players.Count / 2; i < players.Count; i++)
                {
                    if (players[i].win.Count == bestWin)
                    {
                        bestWinCount++;
                    }
                }
                if (bestWinCount == 1)
                {
                    players[bestIdx].player.Points += players.Count * 100 / 2;
                    for (int i = 0; i < (players.Count / 2); i++)
                    {
                        players[i].player.Points -= 100;
                    }
                }
            }

            if (room.type == ROOM_TYPE.ROOM_MATCH)
            {
                Boolean isValid = true;
                //judge if the game is valid
                if (half1 > half2 || half1Total > half2Total)
                {
                    if ((players[0].player.Points + players[1].player.Points + players[2].player.Points) <
                        (playersPoints[0] + playersPoints[1] + playersPoints[2]))
                    {
                        isValid = false;
                    }
                    else
                    {
                        teamTezhandui1.Points += 3;
                        teamTezhandui1.Win[teamTezhandui1.WinCur] = half1 - half2;
                        teamTezhandui1.WinCur++;
                        teamTezhandui1.WinCur %= 10;
                    }
                }
                else if (half1 < half2 || half1Total < half2Total)
                {
                    if ((players[3].player.Points + players[4].player.Points + players[5].player.Points) <
                        (playersPoints[3] + playersPoints[4] + playersPoints[5]))
                    {
                        isValid = false;
                    }
                    else
                    {
                        teamTezhandui2.Points += 3;
                        teamTezhandui2.Win[teamTezhandui2.WinCur] = half2 - half1;
                        teamTezhandui2.WinCur++;
                        teamTezhandui2.WinCur %= 10;
                    }
                }
                else
                {
                    //tie
                    isValid = false;
                }

                if (isValid)
                {
                    using (var redis = RedisMgr.Instance.GetClient())
                    {
                        IRedisTypedClient<TeamTezhandui> redisT = redis.As<TeamTezhandui>();
                        redisT.StoreAsHash(teamTezhandui1);
                        redisT.StoreAsHash(teamTezhandui2);
                    }
                }
                else
                {
                    for (int i = 0; i < players.Count; i++)
                    {
                        //restore points
                        players[i].player.Points = playersPoints[i];
                    }
                    return;
                }
            }

            //save to DB
            for (int i = 0; i < players.Count; i++)
            {
                if (players[i].player.Points < 0)
                {
                    players[i].player.Points = 0;
                }
                
                DateTime endTime = DateTime.Now;
                TimeSpan interval = endTime.Subtract(startTime);
                long intervalSec = 60 * 60 * interval.Hours + 60 * interval.Minutes + interval.Seconds;
                playersTezhandui[i].Time += intervalSec;
                if (room.type == ROOM_TYPE.ROOM_GAME)
                {
                    playersTezhandui[i].WinPointsGame += players[i].player.Points - playersPoints[i];
                }
                else
                {
                    playersTezhandui[i].WinPointsMatch += players[i].player.Points - playersPoints[i];
                }
                

                using (var redis = RedisMgr.Instance.GetClient())
                {
                    IRedisTypedClient<Player> redisT = redis.As<Player>();
                    redisT.StoreAsHash(players[i].player);
                    //Console.WriteLine("player " + players[i].player.Id + "point=" + players[i].player.Points + ",save to DB");
                    IRedisTypedClient<PlayerTezhandui> redisT1 = redis.As<PlayerTezhandui>();
                    redisT1.StoreAsHash(playersTezhandui[i]);
                }
            }
        }

        public void CaculateCards()
        {
            lock (this)
            {
                switch (state)
                {
                    case GAME_STATE.DISTRIBUTE://->offend
                        DetermineOffenderDefenders();
                        state = GAME_STATE.OFFEND;
                        break;
                    case GAME_STATE.OFFEND://->defend
                        OutputCards();
                        state = GAME_STATE.DEFEND;
                        timer.StartDefend();
                        break;
                    case GAME_STATE.DEFEND://->compare
                        OutputCards();
                        CompareCards();
                        state = GAME_STATE.COMPARE;
                        timer.StartCompare();
                        break;
                    case GAME_STATE.COMPARE://->offend or ->end
                        DetermineOffenderDefenders();
                        if (CheckEnd())
                        {
                            CaculateResult();
                            state = GAME_STATE.END;
                            timer.StartEnd();
                        }
                        else
                        {
                            state = GAME_STATE.OFFEND;
                        }
                        break;
                    default:
                        Console.WriteLine("CaculateCards wrong state = " + state);
                        break;
                }
            }
        }

        public GameMessageResp GameProcessMessage(GameMessageReq req)
        {
            GameMessageResp resp = new GameMessageResp();
            switch (req.cmd)
            {
                case GAME_CMD.GET_GAME_STATE:
                    resp.success = true;
                    break;
                case GAME_CMD.OUTPUT_CARD:
                    if (state == GAME_STATE.OFFEND)
                    {
                        if (offender.playerIdx == req.playerIdx && offender.output == -1)
                        {
                            offender.output = req.output;
                            resp.success = true;
                            CaculateCards();
                        }
                    }
                    else if (state == GAME_STATE.DEFEND)
                    {
                        foreach (Playing defender in defenders)
                        {
                            if (defender.playerIdx == req.playerIdx && defender.output == -1)
                            {
                                defender.output = req.output;
                                //charge cheating points
                                if (req.cheat)
                                {
                                    players[defender.playerIdx].player.Points -= 100;
                                }
                                resp.success = true;
                            }
                        }
                        int alreadyOutput = 0;
                        foreach (Playing defender in defenders)
                        {
                            if (defender.output != -1)
                            {
                                alreadyOutput++;
                            }
                        }
                        if (alreadyOutput == defenders.Count)
                        {
                            CaculateCards();
                        }
                    }
                    resp.success = false;
                    break;
                default:
                    resp.success = false;
                    break;
            }
            resp.state = state;
            resp.distributeCount = timer.distributeCount;
            resp.offendCount = timer.offendCount;
            resp.defendCount = timer.defendCount;
            resp.compareCount = timer.compareCount;
            resp.endCount = timer.endCount;
            resp.players = players;
            resp.offender = offender;
            resp.defenders = defenders;
            if (req.playerIdx == -1)
            {
                foreach (GamePlayer gamePlayer in players)
                {
                    if (gamePlayer.player.Id.Equals(req.usrId))
                    {
                        resp.playerIdx = gamePlayer.playerIdx;
                        break;
                    }
                }
            }
            if (req.playerIdx != -1)
            {
                resp.playerIdx = req.playerIdx;
                resp.rest = players[req.playerIdx].rest;
            }
            if (resp.playerIdx != -1)
            {
                resp.rest = players[resp.playerIdx].rest;
            }
            return resp;
        }
    }
}
